""" ClearML configuration wizard"""
from __future__ import print_function

import argparse
import os
import urllib
from typing import Tuple, List, Dict, Optional, Any
from urllib.parse import ParseResult

import six
from pathlib2 import Path
from six.moves import input
from six.moves.urllib.parse import urlparse

from clearml.backend_api.session import Session
from clearml.backend_api.session.defs import ENV_HOST
from clearml.backend_config import Config
from clearml.backend_config.defs import (
    LOCAL_CONFIG_FILES,
    LOCAL_CONFIG_FILE_OVERRIDE_VAR,
)
from clearml.config import config_obj
from clearml.utilities.pyhocon import ConfigFactory, ConfigMissingException

description = (
    "\n"
    "Please create new clearml credentials through the settings page in "
    "your `clearml-server` web app (e.g. http://localhost:8080//settings/workspace-configuration) \n"
    "Or create a free account at https://app.clear.ml/settings/workspace-configuration\n\n"
    'In settings page, press "Create new credentials", then press "Copy to clipboard".\n'
    "\n"
    "Paste copied configuration here:\n"
)

host_description = """
Editing configuration file: {CONFIG_FILE}
Enter the url of the clearml-server's Web service, for example: {HOST}
"""

# noinspection PyBroadException
try:
    def_host = ENV_HOST.get(default=config_obj.get("api.web_server")) or "http://localhost:8080"
except Exception:
    def_host = "http://localhost:8080"


def validate_file(string: str) -> str:
    if not string:
        raise argparse.ArgumentTypeError("expected a valid file path")
    return string


def main() -> None:
    default_config_file = LOCAL_CONFIG_FILE_OVERRIDE_VAR.get()
    if not default_config_file:
        for f in LOCAL_CONFIG_FILES:
            default_config_file = f
            if os.path.exists(os.path.expanduser(os.path.expandvars(f))):
                break

    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument(
        "--file",
        "-F",
        help="Target configuration file path (default is %(default)s)",
        default=default_config_file,
        type=validate_file,
    )

    args = p.parse_args()

    print("ClearML SDK setup process")

    conf_file = Path(os.path.expanduser(args.file)).absolute()
    if conf_file.exists() and conf_file.is_file() and conf_file.stat().st_size > 0:
        print("Configuration file already exists: {}".format(str(conf_file)))
        print("Leaving setup, feel free to edit the configuration file.")
        return
    print(description, end="")
    sentinel = ""
    parse_input = ""

    if os.environ.get("JPY_PARENT_PID"):
        # When running from a colab instance and calling clearml-init
        # colab will squish the api credentials into a single line
        # The regex splits this single line based on 2 spaces or more
        import re

        api_input = input()
        parse_input = "\n".join(re.split(r" {2,}", api_input))
    else:
        for line in iter(input, sentinel):
            parse_input += line + "\n"
            if line.rstrip() == "}":
                break

    credentials = None
    api_server = None
    web_server = None
    files_server = None
    # noinspection PyBroadException
    try:
        parsed = ConfigFactory.parse_string(parse_input)
        if parsed:
            # Take the credentials in raw form or from api section
            credentials = get_parsed_field(parsed, ["credentials"])
            api_server = get_parsed_field(parsed, ["api_server", "host"])
            web_server = get_parsed_field(parsed, ["web_server"])  # TODO: if previous fails, this will fail too
            files_server = get_parsed_field(parsed, ["files_server"])
    except Exception:
        credentials = credentials or None
        api_server = api_server or None
        web_server = web_server or None
        files_server = files_server or None

    while not credentials or set(credentials) != {"access_key", "secret_key"}:
        print("Could not parse credentials, please try entering them manually.")
        credentials = read_manual_credentials()

    print(
        'Detected credentials key="{}" secret="{}"'.format(
            credentials["access_key"], credentials["secret_key"][0:4] + "***"
        )
    )
    web_input = True
    if web_server:
        host = web_server
    elif api_server:
        web_input = False
        host = api_server
    else:
        print(
            host_description.format(
                CONFIG_FILE=args.file,
                HOST=def_host,
            )
        )
        host = input_url("WEB Host", "")

    parsed_host = verify_url(host)
    api_host, files_host, web_host = parse_known_host(parsed_host)

    hosts_dict = {"API": api_server, "Files": files_server, "Web": web_server}

    infered_hosts_dict = {"API": api_host, "Files": files_host, "Web": web_host}

    for host_type, url in six.iteritems(hosts_dict):
        if url is None or not (url.startswith("http://") or url.startswith("https://")):
            infered_host_url = infered_hosts_dict[host_type]
            if infered_host_url != "":
                hosts_dict[host_type] = infered_host_url
            else:
                hosts_dict[host_type] = input_url(host_type)

    api_host, files_host, web_host = (
        hosts_dict["API"],
        hosts_dict["Files"],
        hosts_dict["Web"],
    )

    # one of these two we configured
    if not web_input:
        web_host = input_url("Web Application Host", web_host)
    else:
        if web_input is True and not web_host:
            web_host = host

    print(
        "\nClearML Hosts configuration:\nWeb App: {}\nAPI: {}\nFile Store: {}\n".format(web_host, api_host, files_host)
    )

    if len({web_host, api_host, files_host}) != 3:
        raise ValueError("All three server URLs should be distinct")

    retry = 1
    max_retries = 2
    while retry <= max_retries:  # Up to 2 tries by the user
        if verify_credentials(api_host, credentials):
            break
        retry += 1
        if retry < max_retries + 1:
            credentials = read_manual_credentials()
    else:
        print("Exiting setup without creating configuration file")
        return

    # noinspection PyBroadException
    try:
        default_sdk_conf = Path(__file__).absolute().parents[2] / "config/default/sdk.conf"
        with open(str(default_sdk_conf), "rt") as f:
            default_sdk = f.read()
    except Exception:
        print("Error! Could not read default configuration file")
        return
    # noinspection PyBroadException
    try:
        with open(str(conf_file), "wt") as f:
            header = (
                "# ClearML SDK configuration file\n"
                "api {\n"
                "    # Notice: 'host' is the api server (default port 8008), not the web server.\n"
                "    api_server: %s\n"
                "    web_server: %s\n"
                "    files_server: %s\n"
                "    # Credentials are generated using the webapp, %s/settings\n"
                "    # Override with os environment: CLEARML_API_ACCESS_KEY / CLEARML_API_SECRET_KEY\n"
                '    credentials {"access_key": "%s", "secret_key": "%s"}\n'
                "}\n"
                "sdk "
                % (
                    api_host,
                    web_host,
                    files_host,
                    web_host,
                    credentials["access_key"],
                    credentials["secret_key"],
                )
            )
            f.write(header)
            f.write(default_sdk)
    except Exception:
        print("Error! Could not write configuration file at: {}".format(str(conf_file)))
        return

    print("\nNew configuration stored in {}".format(str(conf_file)))
    print("ClearML setup completed successfully.")


def parse_known_host(parsed_host: Any) -> Tuple[str, str, str]:
    if parsed_host.netloc.startswith("demoapp."):
        # this is our demo server
        api_host = parsed_host.scheme + "://" + parsed_host.netloc.replace("demoapp.", "demoapi.", 1) + parsed_host.path
        web_host = parsed_host.scheme + "://" + parsed_host.netloc + parsed_host.path
        files_host = (
            parsed_host.scheme + "://" + parsed_host.netloc.replace("demoapp.", "demofiles.", 1) + parsed_host.path
        )
    elif parsed_host.netloc.startswith("app."):
        # this is our application server
        api_host = parsed_host.scheme + "://" + parsed_host.netloc.replace("app.", "api.", 1) + parsed_host.path
        web_host = parsed_host.scheme + "://" + parsed_host.netloc + parsed_host.path
        files_host = parsed_host.scheme + "://" + parsed_host.netloc.replace("app.", "files.", 1) + parsed_host.path
    elif parsed_host.netloc.startswith("demoapi."):
        print(
            "{} is the api server, we need the web server. Replacing 'demoapi.' with 'demoapp.'".format(
                parsed_host.netloc
            )
        )
        api_host = parsed_host.scheme + "://" + parsed_host.netloc + parsed_host.path
        web_host = parsed_host.scheme + "://" + parsed_host.netloc.replace("demoapi.", "demoapp.", 1) + parsed_host.path
        files_host = (
            parsed_host.scheme + "://" + parsed_host.netloc.replace("demoapi.", "demofiles.", 1) + parsed_host.path
        )
    elif parsed_host.netloc.startswith("api."):
        print("{} is the api server, we need the web server. Replacing 'api.' with 'app.'".format(parsed_host.netloc))
        api_host = parsed_host.scheme + "://" + parsed_host.netloc + parsed_host.path
        web_host = parsed_host.scheme + "://" + parsed_host.netloc.replace("api.", "app.", 1) + parsed_host.path
        files_host = parsed_host.scheme + "://" + parsed_host.netloc.replace("api.", "files.", 1) + parsed_host.path
    elif parsed_host.port == 8008:
        print("Port 8008 is the api port. Replacing 8008 with 8080 for Web application")
        api_host = parsed_host.scheme + "://" + parsed_host.netloc + parsed_host.path
        web_host = parsed_host.scheme + "://" + parsed_host.netloc.replace(":8008", ":8080", 1) + parsed_host.path
        files_host = parsed_host.scheme + "://" + parsed_host.netloc.replace(":8008", ":8081", 1) + parsed_host.path
    elif parsed_host.port == 8080:
        print("Port 8080 is the web port. Replacing 8080 with 8008 for API server")
        api_host = parsed_host.scheme + "://" + parsed_host.netloc.replace(":8080", ":8008", 1) + parsed_host.path
        web_host = parsed_host.scheme + "://" + parsed_host.netloc + parsed_host.path
        files_host = parsed_host.scheme + "://" + parsed_host.netloc.replace(":8080", ":8081", 1) + parsed_host.path
    elif parsed_host.port is None:
        print("Web app hosted on standard port using " + parsed_host.scheme + " protocol.")
        print("Assuming files and api ports are unchanged and use the same (" + parsed_host.scheme + ") protocol")
        api_host = parsed_host.scheme + "://" + parsed_host.netloc + ":8008" + parsed_host.path
        web_host = parsed_host.scheme + "://" + parsed_host.netloc + parsed_host.path
        files_host = parsed_host.scheme + "://" + parsed_host.netloc + ":8081" + parsed_host.path
    else:
        print("Warning! Could not parse host name")
        api_host = ""
        web_host = ""
        files_host = ""

    return api_host, files_host, web_host


def verify_credentials(api_host: str, credentials: dict) -> bool:
    """check if the credentials are valid"""
    # noinspection PyBroadException
    try:
        print("Verifying credentials ...")
        if api_host:
            Session(
                api_key=credentials["access_key"],
                secret_key=credentials["secret_key"],
                host=api_host,
                http_retries_config={"total": 2},
            )
            print("Credentials verified!")
            return True
        else:
            print("Can't verify credentials")
            return False
    except Exception:
        print(
            "Error: could not verify credentials: key={} secret={}".format(
                credentials.get("access_key"), credentials.get("secret_key")
            )
        )
        return False


def get_parsed_field(parsed_config: Config, fields: List[str]) -> Any:
    """
    Parsed the value from web profile page, 'copy to clipboard' option
    :param parsed_config: The parsed value from the web ui
    :type parsed_config: Config object
    :param fields: list of values to parse, will parse by the list order
    :type fields: List[str]
    :return: parsed value if found, None else
    """
    try:
        return parsed_config.get("api").get(fields[0])
    except ConfigMissingException:  # fallback - try to parse the field like it was in web older version
        if len(fields) == 1:
            return parsed_config.get(fields[0])
        elif len(fields) == 2:
            return parsed_config.get(fields[1])
        else:
            return None


def read_manual_credentials() -> Dict[str, str]:
    print("Enter user access key: ", end="")
    access_key = input()
    print("Enter user secret: ", end="")
    secret_key = input()
    return {"access_key": access_key, "secret_key": secret_key}


def input_url(host_type: str, host: str = None) -> str:
    while True:
        print(
            "{} configured to: {}".format(host_type, "[{}] ".format(host) if host else ""),
            end="",
        )
        parse_input = input()
        if host and (not parse_input or parse_input.lower() == "yes" or parse_input.lower() == "y"):
            break
        parsed_host = verify_url(parse_input) if parse_input else None
        if parse_input and parsed_host:
            host = parsed_host.scheme + "://" + parsed_host.netloc + parsed_host.path
            break
    return host


def input_host_port(host_type: str, parsed_host: urllib.parse.ParseResult) -> str:
    print("Enter port for {} host ".format(host_type), end="")
    replace_port = input().lower()
    return (
        parsed_host.scheme
        + "://"
        + parsed_host.netloc
        + (":{}".format(replace_port) if replace_port else "")
        + parsed_host.path
    )


def verify_url(parse_input: str) -> Optional[ParseResult]:
    # noinspection PyBroadException
    try:
        if not parse_input.startswith("http://") and not parse_input.startswith("https://"):
            # if we have a specific port, use http prefix, otherwise assume https
            if ":" in parse_input:
                parse_input = "http://" + parse_input
            else:
                parse_input = "https://" + parse_input
        parsed_host = urlparse(parse_input)
        if parsed_host.scheme not in ("http", "https"):
            parsed_host = None
    except Exception:
        parsed_host = None
        print(
            "Could not parse url {}\nEnter your clearml-server host: ".format(parse_input),
            end="",
        )
    return parsed_host


if __name__ == "__main__":
    main()
